//$Id: Configuration.java 10842 2006-11-17 18:38:15Z max.andersen@jboss.com $
package org.hibernate.cfg;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Attribute;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.hibernate.EmptyInterceptor;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.InvalidMappingException;
import org.hibernate.MappingException;
import org.hibernate.MappingNotFoundException;
import org.hibernate.SessionFactory;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.engine.FilterDefinition;
import org.hibernate.engine.Mapping;
import org.hibernate.event.AutoFlushEventListener;
import org.hibernate.event.DeleteEventListener;
import org.hibernate.event.DirtyCheckEventListener;
import org.hibernate.event.EventListeners;
import org.hibernate.event.EvictEventListener;
import org.hibernate.event.FlushEntityEventListener;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.InitializeCollectionEventListener;
import org.hibernate.event.LoadEventListener;
import org.hibernate.event.LockEventListener;
import org.hibernate.event.MergeEventListener;
import org.hibernate.event.PersistEventListener;
import org.hibernate.event.PostDeleteEventListener;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostLoadEventListener;
import org.hibernate.event.PostUpdateEventListener;
import org.hibernate.event.PreDeleteEventListener;
import org.hibernate.event.PreInsertEventListener;
import org.hibernate.event.PreLoadEventListener;
import org.hibernate.event.PreUpdateEventListener;
import org.hibernate.event.RefreshEventListener;
import org.hibernate.event.ReplicateEventListener;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.impl.MySessionFactoryImpl;
import org.hibernate.mapping.AuxiliaryDatabaseObject;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.IdentifierCollection;
import org.hibernate.mapping.Index;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.secure.JACCConfiguration;
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata;
import org.hibernate.type.SerializationException;
import org.hibernate.type.Type;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.ConfigHelper;
import org.hibernate.util.PropertiesHelper;
import org.hibernate.util.ReflectHelper;
import org.hibernate.util.SerializationHelper;
import org.hibernate.util.StringHelper;
import org.hibernate.util.XMLHelper;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

public class MyConfiguration extends Configuration implements Serializable {

	private static Log log = LogFactory.getLog(MyConfiguration.class);

	private Interceptor interceptor;
	private Properties properties;
	private EntityResolver entityResolver;
	private EntityNotFoundDelegate entityNotFoundDelegate;
	private EventListeners eventListeners;

	private transient Mapping mapping = buildMapping();

	protected final SettingsFactory settingsFactory;

	protected void reset() {

		interceptor = EmptyInterceptor.INSTANCE;
		properties = Environment.getProperties();
		entityResolver = XMLHelper.DEFAULT_DTD_RESOLVER;
		eventListeners = new EventListeners();
		super.reset();
	}

	/**
	 * Set a custom entity resolver. This entity resolver must be set before
	 * addXXX(misc) call. Default value is
	 * {@link org.hibernate.util.DTDEntityResolver}
	 * 
	 * @param entityResolver
	 *            entity resolver to use
	 */
	public void setEntityResolver(EntityResolver entityResolver) {
		this.entityResolver = entityResolver;
	}

	public EntityResolver getEntityResolver() {
		return entityResolver;
	}

	/**
	 * Retrieve the user-supplied delegate to handle non-existent entity
	 * scenarios. May be null.
	 * 
	 * @return The user-supplied delegate
	 */
	public EntityNotFoundDelegate getEntityNotFoundDelegate() {
		return entityNotFoundDelegate;
	}

	/**
	 * Specify a user-supplied delegate to be used to handle scenarios where an
	 * entity could not be located by specified id. This is mainly intended for
	 * EJB3 implementations to be able to control how proxy initialization
	 * errors should be handled...
	 * 
	 * @param entityNotFoundDelegate
	 *            The delegate to use
	 */
	public void setEntityNotFoundDelegate(
			EntityNotFoundDelegate entityNotFoundDelegate) {
		this.entityNotFoundDelegate = entityNotFoundDelegate;
	}

	/**
	 * Read mappings from a particular XML file
	 * 
	 * @param xmlFile
	 *            a path to a file
	 * @return this (for method chaining purposes)
	 * @throws org.hibernate.MappingException
	 *             Indicates inability to locate or parse the specified mapping
	 *             file.
	 */
	public Configuration addFile(File xmlFile) throws MappingException {
		log.info("Reading mappings from file: " + xmlFile.getPath());
		if (!xmlFile.exists()) {
			throw new MappingNotFoundException("file", xmlFile.toString());
		}
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader(
					xmlFile.toString(), errors, entityResolver).read(xmlFile);
			if (errors.size() != 0) {
				throw new InvalidMappingException("file", xmlFile.toString(),
						(Throwable) errors.get(0));
			}
			add(doc);
			return this;
		} catch (InvalidMappingException e) {
			throw e;
		} catch (MappingNotFoundException e) {
			throw e;
		} catch (Exception e) {
			throw new InvalidMappingException("file", xmlFile.toString(), e);
		}
	}

	/**
	 * Add a cached mapping file. A cached file is a serialized representation
	 * of the DOM structure of a particular mapping. It is saved from a previous
	 * call as a file with the name <tt>xmlFile + ".bin"</tt> where xmlFile is
	 * the name of the original mapping file. </p> If a cached
	 * <tt>xmlFile + ".bin"</tt> exists and is newer than <tt>xmlFile</tt> the
	 * <tt>".bin"</tt> file will be read directly. Otherwise xmlFile is read and
	 * then serialized to <tt>xmlFile + ".bin"</tt> for use the next time.
	 * 
	 * @param xmlFile
	 *            The cacheable mapping file to be added.
	 * @return this (for method chaining purposes)
	 * @throws MappingException
	 *             Indicates problems reading the cached file or processing the
	 *             non-cached file.
	 */
	public Configuration addCacheableFile(File xmlFile) throws MappingException {
		try {
			File cachedFile = new File(xmlFile.getAbsolutePath() + ".bin");
			org.dom4j.Document doc = null;

			final boolean useCachedFile = xmlFile.exists()
					&& cachedFile.exists()
					&& xmlFile.lastModified() < cachedFile.lastModified();

			if (useCachedFile) {
				try {
					log.info("Reading mappings from cache file: " + cachedFile);
					doc = (org.dom4j.Document) SerializationHelper
							.deserialize(new FileInputStream(cachedFile));
				} catch (SerializationException e) {
					log.warn("Could not deserialize cache file: "
							+ cachedFile.getPath(), e);
				} catch (FileNotFoundException e) {
					log.warn("I/O reported cached file could not be found : "
							+ cachedFile.getPath(), e);
				}
			}

			// if doc is null, then for whatever reason, the cached file cannot
			// be used...
			if (doc == null) {
				if (!xmlFile.exists()) {
					throw new MappingNotFoundException("file", xmlFile
							.toString());
				}

				log.info("Reading mappings from file: " + xmlFile);
				List errors = new ArrayList();
				try {
					doc = xmlHelper.createSAXReader(xmlFile.getAbsolutePath(),
							errors, entityResolver).read(xmlFile);
					if (errors.size() != 0) {
						throw new MappingException("invalid mapping",
								(Throwable) errors.get(0));
					}
				} catch (DocumentException e) {
					throw new MappingException("invalid mapping", e);
				}

				try {
					log.debug("Writing cache file for: " + xmlFile + " to: "
							+ cachedFile);
					SerializationHelper.serialize((Serializable) doc,
							new FileOutputStream(cachedFile));
				} catch (SerializationException e) {
					log.warn("Could not write cached file: " + cachedFile, e);
				} catch (FileNotFoundException e) {
					log.warn("I/O reported error writing cached file : "
							+ cachedFile.getPath(), e);
				}
			}

			add(doc);
			return this;

		} catch (InvalidMappingException e) {
			throw e;
		} catch (MappingNotFoundException e) {
			throw e;
		} catch (Exception e) {
			throw new InvalidMappingException("file", xmlFile.toString(), e);
		}
	}

	/**
	 * Read mappings from a <tt>String</tt>
	 * 
	 * @param xml
	 *            an XML string
	 * @return this (for method chaining purposes)
	 * @throws org.hibernate.MappingException
	 *             Indicates problems parsing the given XML string
	 */
	public Configuration addXML(String xml) throws MappingException {
		if (log.isDebugEnabled()) {
			log.debug("Mapping XML:\n" + xml);
		}
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader("XML String",
					errors, entityResolver).read(new StringReader(xml));
			if (errors.size() != 0) {
				throw new MappingException("invalid mapping",
						(Throwable) errors.get(0));
			}
			add(doc);
		} catch (DocumentException e) {
			throw new MappingException(
					"Could not parse mapping document in XML string", e);
		}
		return this;
	}

	/**
	 * Read mappings from an {@link java.io.InputStream}.
	 * 
	 * @param xmlInputStream
	 *            The input stream containing a DOM.
	 * @return this (for method chaining purposes)
	 * @throws MappingException
	 *             Indicates problems reading the stream, or processing the
	 *             contained mapping document.
	 */
	public Configuration addInputStream(InputStream xmlInputStream)
			throws MappingException {
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader(
					"XML InputStream", errors, entityResolver).read(
					new InputSource(xmlInputStream));
			if (errors.size() != 0) {
				throw new InvalidMappingException("invalid mapping", null,
						(Throwable) errors.get(0));
			}
			add(doc);
			return this;
		} catch (DocumentException e) {
			throw new InvalidMappingException("input stream", null, e);
		} finally {
			try {
				xmlInputStream.close();
			} catch (IOException ioe) {
				log.warn("Could not close input stream", ioe);
			}
		}
	}

	private Iterator iterateGenerators(Dialect dialect) throws MappingException {

		TreeMap generators = new TreeMap();
		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		Iterator iter = classes.values().iterator();
		while (iter.hasNext()) {
			PersistentClass pc = (PersistentClass) iter.next();

			if (!pc.isInherited()) {

				IdentifierGenerator ig = pc.getIdentifier()
						.createIdentifierGenerator(dialect, defaultCatalog,
								defaultSchema, (RootClass) pc);

				if (ig instanceof PersistentIdentifierGenerator) {
					generators.put(((PersistentIdentifierGenerator) ig)
							.generatorKey(), ig);
				}

			}
		}

		iter = collections.values().iterator();
		while (iter.hasNext()) {
			Collection collection = (Collection) iter.next();

			if (collection.isIdentified()) {

				IdentifierGenerator ig = ((IdentifierCollection) collection)
						.getIdentifier().createIdentifierGenerator(dialect,
								defaultCatalog, defaultSchema, null);

				if (ig instanceof PersistentIdentifierGenerator) {
					generators.put(((PersistentIdentifierGenerator) ig)
							.generatorKey(), ig);
				}

			}
		}

		return generators.values().iterator();
	}

	/**
	 * Generate DDL for dropping tables
	 * 
	 * @see org.hibernate.tool.hbm2ddl.SchemaExport
	 */
	public String[] generateDropSchemaScript(Dialect dialect)
			throws HibernateException {

		secondPassCompile();

		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		ArrayList script = new ArrayList(50);

		// drop them in reverse order in case db needs it done that way...
		ListIterator itr = auxiliaryDatabaseObjects
				.listIterator(auxiliaryDatabaseObjects.size());
		while (itr.hasPrevious()) {
			AuxiliaryDatabaseObject object = (AuxiliaryDatabaseObject) itr
					.previous();
			if (object.appliesToDialect(dialect)) {
				script.add(object.sqlDropString(dialect, defaultCatalog,
						defaultSchema));
			}
		}

		if (dialect.dropConstraints()) {
			Iterator iter = getTableMappings();
			while (iter.hasNext()) {
				Table table = (Table) iter.next();
				if (table.isPhysicalTable()) {
					Iterator subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.isPhysicalConstraint()) {
							script.add(fk.sqlDropString(dialect,
									defaultCatalog, defaultSchema));
						}
					}
				}
			}
		}

		Iterator iter = getTableMappings();
		while (iter.hasNext()) {

			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {

				/*
				 * Iterator subIter = table.getIndexIterator(); while (
				 * subIter.hasNext() ) { Index index = (Index) subIter.next();
				 * if ( !index.isForeignKey() ||
				 * !dialect.hasImplicitIndexForForeignKey() ) { script.add(
				 * index.sqlDropString(dialect) ); } }
				 */

				script.add(table.sqlDropString(dialect, defaultCatalog,
						defaultSchema));

			}

		}

		iter = iterateGenerators(dialect);
		while (iter.hasNext()) {
			String[] lines = ((PersistentIdentifierGenerator) iter.next())
					.sqlDropStrings(dialect);
			for (int i = 0; i < lines.length; i++) {
				script.add(lines[i]);
			}
		}

		return ArrayHelper.toStringArray(script);
	}

	/**
	 * Generate DDL for creating tables
	 * 
	 * @see org.hibernate.tool.hbm2ddl.SchemaExport
	 */
	public String[] generateSchemaCreationScript(Dialect dialect)
			throws HibernateException {
		secondPassCompile();

		ArrayList script = new ArrayList(50);
		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		Iterator iter = getTableMappings();
		while (iter.hasNext()) {
			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {
				script.add(table.sqlCreateString(dialect, mapping,
						defaultCatalog, defaultSchema));
				Iterator comments = table.sqlCommentStrings(dialect,
						defaultCatalog, defaultSchema);
				while (comments.hasNext()) {
					script.add(comments.next());
				}
			}
		}

		iter = getTableMappings();
		while (iter.hasNext()) {
			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {

				if (!dialect.supportsUniqueConstraintInCreateAlterTable()) {
					Iterator subIter = table.getUniqueKeyIterator();
					while (subIter.hasNext()) {
						UniqueKey uk = (UniqueKey) subIter.next();
						String constraintString = uk.sqlCreateString(dialect,
								mapping, defaultCatalog, defaultSchema);
						if (constraintString != null)
							script.add(constraintString);
					}
				}

				Iterator subIter = table.getIndexIterator();
				while (subIter.hasNext()) {
					Index index = (Index) subIter.next();
					script.add(index.sqlCreateString(dialect, mapping,
							defaultCatalog, defaultSchema));
				}

				if (dialect.hasAlterTable()) {
					subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.isPhysicalConstraint()) {
							script.add(fk.sqlCreateString(dialect, mapping,
									defaultCatalog, defaultSchema));
						}
					}
				}

			}
		}

		iter = iterateGenerators(dialect);
		while (iter.hasNext()) {
			String[] lines = ((PersistentIdentifierGenerator) iter.next())
					.sqlCreateStrings(dialect);
			for (int i = 0; i < lines.length; i++) {
				script.add(lines[i]);
			}
		}

		Iterator itr = auxiliaryDatabaseObjects.iterator();
		while (itr.hasNext()) {
			AuxiliaryDatabaseObject object = (AuxiliaryDatabaseObject) itr
					.next();
			if (object.appliesToDialect(dialect)) {
				script.add(object.sqlCreateString(dialect, mapping,
						defaultCatalog, defaultSchema));
			}
		}

		return ArrayHelper.toStringArray(script);
	}

	/**
	 * Generate DDL for altering tables
	 * 
	 * @see org.hibernate.tool.hbm2ddl.SchemaUpdate
	 */
	public String[] generateSchemaUpdateScript(Dialect dialect,
			DatabaseMetadata databaseMetadata) throws HibernateException {
		secondPassCompile();

		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		ArrayList script = new ArrayList(50);

		Iterator iter = getTableMappings();
		while (iter.hasNext()) {
			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {

				TableMetadata tableInfo = databaseMetadata.getTableMetadata(
						table.getName(),
						(table.getSchema() == null) ? defaultSchema : table
								.getSchema(),
						(table.getCatalog() == null) ? defaultCatalog : table
								.getCatalog(), table.isQuoted()

				);
				if (tableInfo == null) {
					script.add(table.sqlCreateString(dialect, mapping,
							defaultCatalog, defaultSchema));
				} else {
					Iterator subiter = table.sqlAlterStrings(dialect, mapping,
							tableInfo, defaultCatalog, defaultSchema);
					while (subiter.hasNext()) {
						script.add(subiter.next());
					}
				}

				Iterator comments = table.sqlCommentStrings(dialect,
						defaultCatalog, defaultSchema);
				while (comments.hasNext()) {
					script.add(comments.next());
				}

			}
		}

		iter = getTableMappings();
		while (iter.hasNext()) {
			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {

				TableMetadata tableInfo = databaseMetadata.getTableMetadata(
						table.getName(), table.getSchema(), table.getCatalog(),
						table.isQuoted());

				if (dialect.hasAlterTable()) {
					Iterator subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.isPhysicalConstraint()) {
							boolean create = tableInfo == null
									|| (tableInfo.getForeignKeyMetadata(fk
											.getName()) == null && (
									// Icky workaround for MySQL bug:
									!(dialect instanceof MySQLDialect) || tableInfo
											.getIndexMetadata(fk.getName()) == null));
							if (create) {
								script.add(fk.sqlCreateString(dialect, mapping,
										defaultCatalog, defaultSchema));
							}
						}
					}
				}

			}

			/*
			 * //broken, 'cos we don't generate these with names in SchemaExport
			 * subIter = table.getIndexIterator(); while ( subIter.hasNext() ) {
			 * Index index = (Index) subIter.next(); if ( !index.isForeignKey()
			 * || !dialect.hasImplicitIndexForForeignKey() ) { if (
			 * tableInfo==null || tableInfo.getIndexMetadata(
			 * index.getFilterName() ) == null ) { script.add(
			 * index.sqlCreateString(dialect, mapping) ); } } } //broken, 'cos
			 * we don't generate these with names in SchemaExport subIter =
			 * table.getUniqueKeyIterator(); while ( subIter.hasNext() ) {
			 * UniqueKey uk = (UniqueKey) subIter.next(); if ( tableInfo==null
			 * || tableInfo.getIndexMetadata( uk.getFilterName() ) == null ) {
			 * script.add( uk.sqlCreateString(dialect, mapping) ); } }
			 */
		}

		iter = iterateGenerators(dialect);
		while (iter.hasNext()) {
			PersistentIdentifierGenerator generator = (PersistentIdentifierGenerator) iter
					.next();
			Object key = generator.generatorKey();
			if (!databaseMetadata.isSequence(key)
					&& !databaseMetadata.isTable(key)) {
				String[] lines = generator.sqlCreateStrings(dialect);
				for (int i = 0; i < lines.length; i++) {
					script.add(lines[i]);
				}
			}
		}

		return ArrayHelper.toStringArray(script);
	}

	public void validateSchema(Dialect dialect,
			DatabaseMetadata databaseMetadata) throws HibernateException {
		secondPassCompile();

		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		Iterator iter = getTableMappings();
		while (iter.hasNext()) {
			Table table = (Table) iter.next();
			if (table.isPhysicalTable()) {

				TableMetadata tableInfo = databaseMetadata.getTableMetadata(
						table.getName(),
						(table.getSchema() == null) ? defaultSchema : table
								.getSchema(),
						(table.getCatalog() == null) ? defaultCatalog : table
								.getCatalog(), table.isQuoted());
				if (tableInfo == null) {
					throw new HibernateException("Missing table: "
							+ table.getName());
				} else {
					table.validateColumns(dialect, mapping, tableInfo);
				}

			}
		}

		iter = iterateGenerators(dialect);
		while (iter.hasNext()) {
			PersistentIdentifierGenerator generator = (PersistentIdentifierGenerator) iter
					.next();
			Object key = generator.generatorKey();
			if (!databaseMetadata.isSequence(key)
					&& !databaseMetadata.isTable(key)) {
				throw new HibernateException("Missing sequence or table: "
						+ key);
			}
		}
	}

	private void validate() throws MappingException {
		Iterator iter = classes.values().iterator();
		while (iter.hasNext()) {
			((PersistentClass) iter.next()).validate(mapping);
		}
		iter = collections.values().iterator();
		while (iter.hasNext()) {
			((Collection) iter.next()).validate(mapping);
		}
	}

	private EventListeners getInitializedEventListeners() {
		EventListeners result = (EventListeners) eventListeners.shallowCopy();
		result.initializeListeners(this);
		return result;
	}

	/**
	 * Return the configured <tt>Interceptor</tt>
	 */
	public Interceptor getInterceptor() {
		return interceptor;
	}

	/**
	 * Get all properties
	 */
	public Properties getProperties() {
		return properties;
	}

	/**
	 * Configure an <tt>Interceptor</tt>
	 */
	public Configuration setInterceptor(Interceptor interceptor) {
		this.interceptor = interceptor;
		return this;
	}

	/**
	 * Specify a completely new set of properties
	 */
	public Configuration setProperties(Properties properties) {
		this.properties = properties;
		return this;
	}

	/**
	 * Set the given properties
	 */
	public Configuration addProperties(Properties extraProperties) {
		this.properties.putAll(extraProperties);
		return this;
	}

	/**
	 * Adds the incoming properties to the internap properties structure, as
	 * long as the internal structure does not already contain an entry for the
	 * given key.
	 * 
	 * @param properties
	 * @return this
	 */
	public Configuration mergeProperties(Properties properties) {
		Iterator itr = properties.entrySet().iterator();
		while (itr.hasNext()) {
			final Map.Entry entry = (Map.Entry) itr.next();
			if (this.properties.containsKey(entry.getKey())) {
				continue;
			}
			this.properties.setProperty((String) entry.getKey(), (String) entry
					.getValue());
		}
		return this;
	}

	/**
	 * Set a property
	 */
	public Configuration setProperty(String propertyName, String value) {
		properties.setProperty(propertyName, value);
		return this;
	}

	/**
	 * Get a property
	 */
	public String getProperty(String propertyName) {
		return properties.getProperty(propertyName);
	}

	private void addProperties(Element parent) {
		Iterator iter = parent.elementIterator("property");
		while (iter.hasNext()) {
			Element node = (Element) iter.next();
			String name = node.attributeValue("name");
			String value = node.getText().trim();
			log.debug(name + "=" + value);
			properties.setProperty(name, value);
			if (!name.startsWith("hibernate")) {
				properties.setProperty("hibernate." + name, value);
			}
		}
		Environment.verifyProperties(properties);
	}

	/**
	 * Use the mappings and properties specified in the given application
	 * resource. The format of the resource is defined in
	 * <tt>hibernate-configuration-3.0.dtd</tt>.
	 * 
	 * @param stream
	 *            Inputstream to be read from
	 * @param resourceName
	 *            The name to use in warning/error messages
	 * @return A configuration configured via the stream
	 * @throws HibernateException
	 */
	protected Configuration doConfigure(InputStream stream, String resourceName)
			throws HibernateException {

		org.dom4j.Document doc;
		try {
			List errors = new ArrayList();
			doc = xmlHelper.createSAXReader(resourceName, errors,
					entityResolver).read(new InputSource(stream));
			if (errors.size() != 0) {
				throw new MappingException("invalid configuration",
						(Throwable) errors.get(0));
			}
		} catch (DocumentException e) {
			throw new HibernateException("Could not parse configuration: "
					+ resourceName, e);
		} finally {
			try {
				stream.close();
			} catch (IOException ioe) {
				log.warn("could not close input stream for: " + resourceName,
						ioe);
			}
		}

		return doConfigure(doc);

	}

	protected Configuration doConfigure(org.dom4j.Document doc)
			throws HibernateException {

		Element sfNode = doc.getRootElement().element("session-factory");
		String name = sfNode.attributeValue("name");
		if (name != null) {
			properties.setProperty(Environment.SESSION_FACTORY_NAME, name);
		}
		addProperties(sfNode);
		parseSessionFactory(sfNode, name);

		Element secNode = doc.getRootElement().element("security");
		if (secNode != null) {
			parseSecurity(secNode);
		}

		log.info("Configured SessionFactory: " + name);
		log.debug("properties: " + properties);

		return this;

	}

	private void parseSessionFactory(Element sfNode, String name) {
		Iterator elements = sfNode.elementIterator();
		while (elements.hasNext()) {
			Element subelement = (Element) elements.next();
			String subelementName = subelement.getName();
			if ("mapping".equals(subelementName)) {
				parseMappingElement(subelement, name);
			} else if ("class-cache".equals(subelementName)) {
				String className = subelement.attributeValue("class");
				Attribute regionNode = subelement.attribute("region");
				final String region = (regionNode == null) ? className
						: regionNode.getValue();
				boolean includeLazy = !"non-lazy".equals(subelement
						.attributeValue("include"));
				setCacheConcurrencyStrategy(className, subelement
						.attributeValue("usage"), region, includeLazy);
			} else if ("collection-cache".equals(subelementName)) {
				String role = subelement.attributeValue("collection");
				Attribute regionNode = subelement.attribute("region");
				final String region = (regionNode == null) ? role : regionNode
						.getValue();
				setCollectionCacheConcurrencyStrategy(role, subelement
						.attributeValue("usage"), region);
			} else if ("listener".equals(subelementName)) {
				parseListener(subelement);
			} else if ("event".equals(subelementName)) {
				parseEvent(subelement);
			}
		}
	}

	protected void parseMappingElement(Element subelement, String name) {
		Attribute rsrc = subelement.attribute("resource");
		Attribute file = subelement.attribute("file");
		Attribute jar = subelement.attribute("jar");
		Attribute pkg = subelement.attribute("package");
		Attribute clazz = subelement.attribute("class");
		if (rsrc != null) {
			log.debug(name + "<-" + rsrc);
			addResource(rsrc.getValue());
		} else if (jar != null) {
			log.debug(name + "<-" + jar);
			addJar(new File(jar.getValue()));
		} else if (pkg != null) {
			throw new MappingException(
					"An AnnotationConfiguration instance is required to use <mapping package=\""
							+ pkg.getValue() + "\"/>");
		} else if (clazz != null) {
			throw new MappingException(
					"An AnnotationConfiguration instance is required to use <mapping class=\""
							+ clazz.getValue() + "\"/>");
		} else {
			if (file == null) {
				throw new MappingException(
						"<mapping> element in configuration specifies no attributes");
			}
			log.debug(name + "<-" + file);
			addFile(file.getValue());
		}
	}

	private void parseSecurity(Element secNode) {
		String contextId = secNode.attributeValue("context");
		setProperty(Environment.JACC_CONTEXTID, contextId);
		log.info("JACC contextID: " + contextId);
		JACCConfiguration jcfg = new JACCConfiguration(contextId);
		Iterator grantElements = secNode.elementIterator();
		while (grantElements.hasNext()) {
			Element grantElement = (Element) grantElements.next();
			String elementName = grantElement.getName();
			if ("grant".equals(elementName)) {
				jcfg.addPermission(grantElement.attributeValue("role"),
						grantElement.attributeValue("entity-name"),
						grantElement.attributeValue("actions"));
			}
		}
	}

	private void parseEvent(Element element) {
		String type = element.attributeValue("type");
		List listeners = element.elements();
		String[] listenerClasses = new String[listeners.size()];
		for (int i = 0; i < listeners.size(); i++) {
			listenerClasses[i] = ((Element) listeners.get(i))
					.attributeValue("class");
		}
		log.debug("Event listeners: " + type + "="
				+ StringHelper.toString(listenerClasses));
		setListeners(type, listenerClasses);
	}

	private void parseListener(Element element) {
		String type = element.attributeValue("type");
		if (type == null) {
			throw new MappingException("No type specified for listener");
		}
		String impl = element.attributeValue("class");
		log.debug("Event listener: " + type + "=" + impl);
		setListeners(type, new String[] { impl });
	}

	public void setListeners(String type, String[] listenerClasses) {
		Object[] listeners = (Object[]) Array.newInstance(eventListeners
				.getListenerClassFor(type), listenerClasses.length);
		for (int i = 0; i < listeners.length; i++) {
			try {
				listeners[i] = ReflectHelper.classForName(listenerClasses[i])
						.newInstance();
			} catch (Exception e) {
				throw new MappingException(
						"Unable to instantiate specified event (" + type
								+ ") listener class: " + listenerClasses[i], e);
			}
		}
		setListeners(type, listeners);
	}

	public void setListener(String type, Object listener) {
		if (listener == null) {
			setListener(type, null);
		} else {
			Object[] listeners = (Object[]) Array.newInstance(eventListeners
					.getListenerClassFor(type), 1);
			listeners[0] = listener;
			setListeners(type, listeners);
		}
	}

	public void setListeners(String type, Object[] listeners) {
		if ("auto-flush".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setAutoFlushEventListeners(new AutoFlushEventListener[] {});
			} else {
				eventListeners
						.setAutoFlushEventListeners((AutoFlushEventListener[]) listeners);
			}
		} else if ("merge".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setMergeEventListeners(new MergeEventListener[] {});
			} else {
				eventListeners
						.setMergeEventListeners((MergeEventListener[]) listeners);
			}
		} else if ("create".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPersistEventListeners(new PersistEventListener[] {});
			} else {
				eventListeners
						.setPersistEventListeners((PersistEventListener[]) listeners);
			}
		} else if ("create-onflush".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPersistOnFlushEventListeners(new PersistEventListener[] {});
			} else {
				eventListeners
						.setPersistOnFlushEventListeners((PersistEventListener[]) listeners);
			}
		} else if ("delete".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setDeleteEventListeners(new DeleteEventListener[] {});
			} else {
				eventListeners
						.setDeleteEventListeners((DeleteEventListener[]) listeners);
			}
		} else if ("dirty-check".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setDirtyCheckEventListeners(new DirtyCheckEventListener[] {});
			} else {
				eventListeners
						.setDirtyCheckEventListeners((DirtyCheckEventListener[]) listeners);
			}
		} else if ("evict".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setEvictEventListeners(new EvictEventListener[] {});
			} else {
				eventListeners
						.setEvictEventListeners((EvictEventListener[]) listeners);
			}
		} else if ("flush".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setFlushEventListeners(new FlushEventListener[] {});
			} else {
				eventListeners
						.setFlushEventListeners((FlushEventListener[]) listeners);
			}
		} else if ("flush-entity".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setFlushEntityEventListeners(new FlushEntityEventListener[] {});
			} else {
				eventListeners
						.setFlushEntityEventListeners((FlushEntityEventListener[]) listeners);
			}
		} else if ("load".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setLoadEventListeners(new LoadEventListener[] {});
			} else {
				eventListeners
						.setLoadEventListeners((LoadEventListener[]) listeners);
			}
		} else if ("load-collection".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setInitializeCollectionEventListeners(new InitializeCollectionEventListener[] {});
			} else {
				eventListeners
						.setInitializeCollectionEventListeners((InitializeCollectionEventListener[]) listeners);
			}
		} else if ("lock".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setLockEventListeners(new LockEventListener[] {});
			} else {
				eventListeners
						.setLockEventListeners((LockEventListener[]) listeners);
			}
		} else if ("refresh".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setRefreshEventListeners(new RefreshEventListener[] {});
			} else {
				eventListeners
						.setRefreshEventListeners((RefreshEventListener[]) listeners);
			}
		} else if ("replicate".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setReplicateEventListeners(new ReplicateEventListener[] {});
			} else {
				eventListeners
						.setReplicateEventListeners((ReplicateEventListener[]) listeners);
			}
		} else if ("save-update".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setSaveOrUpdateEventListeners(new SaveOrUpdateEventListener[] {});
			} else {
				eventListeners
						.setSaveOrUpdateEventListeners((SaveOrUpdateEventListener[]) listeners);
			}
		} else if ("save".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setSaveEventListeners(new SaveOrUpdateEventListener[] {});
			} else {
				eventListeners
						.setSaveEventListeners((SaveOrUpdateEventListener[]) listeners);
			}
		} else if ("update".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setUpdateEventListeners(new SaveOrUpdateEventListener[] {});
			} else {
				eventListeners
						.setUpdateEventListeners((SaveOrUpdateEventListener[]) listeners);
			}
		} else if ("pre-load".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPreLoadEventListeners(new PreLoadEventListener[] {});
			} else {
				eventListeners
						.setPreLoadEventListeners((PreLoadEventListener[]) listeners);
			}
		} else if ("pre-update".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPreUpdateEventListeners(new PreUpdateEventListener[] {});
			} else {
				eventListeners
						.setPreUpdateEventListeners((PreUpdateEventListener[]) listeners);
			}
		} else if ("pre-delete".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPreDeleteEventListeners(new PreDeleteEventListener[] {});
			} else {
				eventListeners
						.setPreDeleteEventListeners((PreDeleteEventListener[]) listeners);
			}
		} else if ("pre-insert".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPreInsertEventListeners(new PreInsertEventListener[] {});
			} else {
				eventListeners
						.setPreInsertEventListeners((PreInsertEventListener[]) listeners);
			}
		} else if ("post-load".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostLoadEventListeners(new PostLoadEventListener[] {});
			} else {
				eventListeners
						.setPostLoadEventListeners((PostLoadEventListener[]) listeners);
			}
		} else if ("post-update".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostUpdateEventListeners(new PostUpdateEventListener[] {});
			} else {
				eventListeners
						.setPostUpdateEventListeners((PostUpdateEventListener[]) listeners);
			}
		} else if ("post-delete".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostDeleteEventListeners(new PostDeleteEventListener[] {});
			} else {
				eventListeners
						.setPostDeleteEventListeners((PostDeleteEventListener[]) listeners);
			}
		} else if ("post-insert".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostInsertEventListeners(new PostInsertEventListener[] {});
			} else {
				eventListeners
						.setPostInsertEventListeners((PostInsertEventListener[]) listeners);
			}
		} else if ("post-commit-update".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostCommitUpdateEventListeners(new PostUpdateEventListener[] {});
			} else {
				eventListeners
						.setPostCommitUpdateEventListeners((PostUpdateEventListener[]) listeners);
			}
		} else if ("post-commit-delete".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostCommitDeleteEventListeners(new PostDeleteEventListener[] {});
			} else {
				eventListeners
						.setPostCommitDeleteEventListeners((PostDeleteEventListener[]) listeners);
			}
		} else if ("post-commit-insert".equals(type)) {
			if (listeners == null) {
				eventListeners
						.setPostCommitInsertEventListeners(new PostInsertEventListener[] {});
			} else {
				eventListeners
						.setPostCommitInsertEventListeners((PostInsertEventListener[]) listeners);
			}
		} else {
			log.warn("Unrecognized listener type [" + type + "]");
		}
	}

	public EventListeners getEventListeners() {
		return eventListeners;
	}

	/**
	 * Create an object-oriented view of the configuration properties
	 */
	public Settings buildSettings() throws HibernateException {
		Properties clone = (Properties) properties.clone();
		PropertiesHelper.resolvePlaceHolders(clone);
		return settingsFactory.buildSettings(clone);
	}

	public Settings buildSettings(Properties props) throws HibernateException {
		return settingsFactory.buildSettings(props);
	}

	private void readObject(ObjectInputStream ois) throws IOException,
			ClassNotFoundException {
		ois.defaultReadObject();
		this.mapping = buildMapping();
		xmlHelper = new XMLHelper();
	}

	//--------------------------------------------------------------------------

	protected MyConfiguration(SettingsFactory settingsFactory) {
		this.settingsFactory = settingsFactory;
		reset();
	}

	public MyConfiguration() {
		this(new SettingsFactory());
	}

	/**
	 * Instantiate a new <tt>SessionFactory</tt>, using the properties and
	 * mappings in this configuration. The <tt>SessionFactory</tt> will be
	 * immutable, so changes made to the <tt>Configuration</tt> after building
	 * the <tt>SessionFactory</tt> will not affect it.
	 * 
	 * @return a new factory for <tt>Session</tt>s
	 * @see org.hibernate.SessionFactory
	 */
	public MySessionFactoryImpl buildSessionFactory() throws HibernateException {
		log.debug("Preparing to build session factory with filters : "
				+ filterDefinitions);
		secondPassCompile();
		validate();
		Environment.verifyProperties(properties);
		Properties copy = new Properties();
		copy.putAll(properties);
		PropertiesHelper.resolvePlaceHolders(copy);
		Settings settings = buildSettings(copy);

		return new MySessionFactoryImpl(this, mapping, settings,
				getInitializedEventListeners());
	}

	public String[] generateDropSchemaScript(Dialect dialect,
			PersistentClass[] exports) {
		// secondPassCompile();

		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		ArrayList script = new ArrayList(50);

		// drop them in reverse order in case db needs it done that way...
		ListIterator itr = auxiliaryDatabaseObjects
				.listIterator(auxiliaryDatabaseObjects.size());
		while (itr.hasPrevious()) {
			AuxiliaryDatabaseObject object = (AuxiliaryDatabaseObject) itr
					.previous();
			if (object.appliesToDialect(dialect)) {
				script.add(object.sqlDropString(dialect, defaultCatalog,
						defaultSchema));
			}
		}

		// Stack st_fk = new Stack();
		// Stack st_tb = new Stack();

		for (int idx = 0; idx < exports.length; idx++) {
			PersistentClass persistentClass = exports[idx];

			if (dialect.dropConstraints()) {
				Table table = persistentClass.getTable();
				if (table.isPhysicalTable()) {
					Iterator subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.getReferencedTable() != null
								&& fk.isPhysicalConstraint()) {
							script.add(fk.sqlDropString(dialect,
									defaultCatalog, defaultSchema));
							// st_tb.push(fk.getReferencedTable());
						}
					}
				}
			}

			Table table = persistentClass.getTable();
			if (table.isPhysicalTable()) {
				// Iterator subIter = table.getIndexIterator();
				// while (subIter.hasNext()) {
				// Index index = (Index) subIter.next();
				// if (!index.isForeignKey()
				// || !dialect.hasImplicitIndexForForeignKey()) {
				// script.add(index.sqlDropString(dialect)); }}
				script.add(table.sqlDropString(dialect, defaultCatalog,
						defaultSchema));

			}

			IdentifierGenerator ig = persistentClass.getIdentifier()
					.createIdentifierGenerator(dialect, defaultCatalog,
							defaultSchema, (RootClass) persistentClass);
			if (ig instanceof PersistentIdentifierGenerator) {
				String[] lines = ((PersistentIdentifierGenerator) ig)
						.sqlDropStrings(dialect);
				for (int i = 0; i < lines.length; i++) {
					script.add(lines[i]);
				}
			}
		}

		return ArrayHelper.toStringArray(script);
	}

	public String[] generateSchemaCreationScript(Dialect dialect,
			PersistentClass[] exports) {
		// secondPassCompile();

		ArrayList script = new ArrayList(50);
		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		for (int idx = 0; idx < exports.length; idx++) {

			PersistentClass persistentClass = exports[idx];

			Table table = (Table) persistentClass.getTable();
			if (table.isPhysicalTable()) {
				script.add(table.sqlCreateString(dialect, mapping,
						defaultCatalog, defaultSchema));
				Iterator comments = table.sqlCommentStrings(dialect,
						defaultCatalog, defaultSchema);
				while (comments.hasNext()) {
					script.add(comments.next());
				}
			}

			if (table.isPhysicalTable()) {
				if (!dialect.supportsUniqueConstraintInCreateAlterTable()) {
					Iterator subIter = table.getUniqueKeyIterator();
					while (subIter.hasNext()) {
						UniqueKey uk = (UniqueKey) subIter.next();
						String constraintString = uk.sqlCreateString(dialect,
								mapping, defaultCatalog, defaultSchema);
						if (constraintString != null)
							script.add(constraintString);
					}
				}

				Iterator subIter = table.getIndexIterator();
				while (subIter.hasNext()) {
					Index index = (Index) subIter.next();
					script.add(index.sqlCreateString(dialect, mapping,
							defaultCatalog, defaultSchema));
				}

				if (dialect.hasAlterTable()) {
					subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.getReferencedTable() != null
								&& fk.isPhysicalConstraint()) {
							script.add(fk.sqlCreateString(dialect, mapping,
									defaultCatalog, defaultSchema));
						}
					}
				}
			}

			IdentifierGenerator ig = persistentClass.getIdentifier()
					.createIdentifierGenerator(dialect, defaultCatalog,
							defaultSchema, (RootClass) persistentClass);
			if (ig instanceof PersistentIdentifierGenerator) {
				String[] lines = ((PersistentIdentifierGenerator) ig)
						.sqlCreateStrings(dialect);
				for (int i = 0; i < lines.length; i++) {
					script.add(lines[i]);
				}
			}
		}
		Iterator itr = auxiliaryDatabaseObjects.iterator();
		while (itr.hasNext()) {
			AuxiliaryDatabaseObject object = (AuxiliaryDatabaseObject) itr
					.next();
			if (object.appliesToDialect(dialect)) {
				script.add(object.sqlCreateString(dialect, mapping,
						defaultCatalog, defaultSchema));
			}
		}

		return ArrayHelper.toStringArray(script);
	}

	public String[] generateSchemaUpdateScript(Dialect dialect,
			DatabaseMetadata databaseMetadata, PersistentClass[] exports) {

		// secondPassCompile();

		String defaultCatalog = properties
				.getProperty(Environment.DEFAULT_CATALOG);
		String defaultSchema = properties
				.getProperty(Environment.DEFAULT_SCHEMA);

		ArrayList script = new ArrayList(50);

		for (int idx = 0; idx < exports.length; idx++) {

			PersistentClass persistentClass = exports[idx];

			Table table = (Table) persistentClass.getTable();
			if (table.isPhysicalTable()) {

				TableMetadata tableInfo = databaseMetadata.getTableMetadata(
						table.getName(),
						(table.getSchema() == null) ? defaultSchema : table
								.getSchema(),
						(table.getCatalog() == null) ? defaultCatalog : table
								.getCatalog(), table.isQuoted()

				);
				if (tableInfo == null) {
					script.add(table.sqlCreateString(dialect, mapping,
							defaultCatalog, defaultSchema));
				} else {
					Iterator subiter = table.sqlAlterStrings(dialect, mapping,
							tableInfo, defaultCatalog, defaultSchema);
					while (subiter.hasNext()) {
						script.add(subiter.next());
					}
				}

				Iterator comments = table.sqlCommentStrings(dialect,
						defaultCatalog, defaultSchema);
				while (comments.hasNext()) {
					script.add(comments.next());
				}

			}

			if (table.isPhysicalTable()) {

				TableMetadata tableInfo = databaseMetadata.getTableMetadata(
						table.getName(), table.getSchema(), table.getCatalog(),
						table.isQuoted());

				if (dialect.hasAlterTable()) {
					Iterator subIter = table.getForeignKeyIterator();
					while (subIter.hasNext()) {
						ForeignKey fk = (ForeignKey) subIter.next();
						if (fk.isPhysicalConstraint()) {
							boolean create = tableInfo == null
									|| (tableInfo.getForeignKeyMetadata(fk
											.getName()) == null && (
									// Icky workaround for MySQL bug:
									!(dialect instanceof MySQLDialect) || tableInfo
											.getIndexMetadata(fk.getName()) == null));
							if (create) {
								script.add(fk.sqlCreateString(dialect, mapping,
										defaultCatalog, defaultSchema));
							}
						}
					}
				}

				/*
				 * //broken, 'cos we don't generate these with names in
				 * SchemaExport subIter = table.getIndexIterator(); while (
				 * subIter.hasNext() ) { Index index = (Index) subIter.next();
				 * if ( !index.isForeignKey() ||
				 * !dialect.hasImplicitIndexForForeignKey() ) { if (
				 * tableInfo==null || tableInfo.getIndexMetadata(
				 * index.getFilterName() ) == null ) { script.add(
				 * index.sqlCreateString(dialect, mapping) ); } } } //broken,
				 * 'cos we don't generate these with names in SchemaExport
				 * subIter = table.getUniqueKeyIterator(); while (
				 * subIter.hasNext() ) { UniqueKey uk = (UniqueKey)
				 * subIter.next(); if ( tableInfo==null ||
				 * tableInfo.getIndexMetadata( uk.getFilterName() ) == null ) {
				 * script.add( uk.sqlCreateString(dialect, mapping) ); } }
				 */
			}

			IdentifierGenerator ig = persistentClass.getIdentifier()
					.createIdentifierGenerator(dialect, defaultCatalog,
							defaultSchema, (RootClass) persistentClass);
			if (ig instanceof PersistentIdentifierGenerator) {
				PersistentIdentifierGenerator generator = (PersistentIdentifierGenerator) ig;
				Object key = generator.generatorKey();
				if (!databaseMetadata.isSequence(key)
						&& !databaseMetadata.isTable(key)) {
					String[] lines = generator.sqlCreateStrings(dialect);
					for (int i = 0; i < lines.length; i++) {
						script.add(lines[i]);
					}
				}
			}
		}
		return ArrayHelper.toStringArray(script);
	}

	public MyConfiguration addOrReplaceResource(String resourceName)
			throws MappingException {
		log.info("Reading mappings from resource : " + resourceName);
		ClassLoader contextClassLoader = Thread.currentThread()
				.getContextClassLoader();
		InputStream rsrc = null;
		if (contextClassLoader != null) {
			rsrc = contextClassLoader.getResourceAsStream(resourceName);
		}
		if (rsrc == null) {
			rsrc = Environment.class.getClassLoader().getResourceAsStream(
					resourceName);
		}

		return addOrReplaceResource(resourceName, contextClassLoader);
	}

	public MyConfiguration addOrReplaceResource(String resourceName,
			ClassLoader classLoader) {
		log.info("Reading mappings from resource: " + resourceName);
		InputStream rsrc = classLoader.getResourceAsStream(resourceName);
		if (rsrc == null) {
			throw new MappingNotFoundException("resource", resourceName);
		}
		try {
			return addOrReplaceInputStream(rsrc);
		} catch (MappingException me) {
			throw new InvalidMappingException("resource", resourceName, me);
		}
	}

	private MyConfiguration addOrReplaceInputStream(InputStream xmlInputStream) {
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader(
					"XML InputStream", errors, entityResolver).read(
					new InputSource(xmlInputStream));
			if (errors.size() != 0) {
				throw new InvalidMappingException("invalid mapping", null,
						(Throwable) errors.get(0));
			}
			addOrReplace(doc);
			return this;
		} catch (DocumentException e) {
			throw new InvalidMappingException("input stream", null, e);
		} finally {
			try {
				xmlInputStream.close();
			} catch (IOException ioe) {
				log.warn("Could not close input stream", ioe);
			}
		}
	}

	public MyConfiguration addOrReplaceFile(String xmlFile)
			throws MappingException {
		return addOrReplaceFile(new File(xmlFile));
	}

	public MyConfiguration addOrReplaceFile(File xmlFile)
			throws MappingException {
		log.info("Reading mappings from file: " + xmlFile.getPath());
		if (!xmlFile.exists()) {
			throw new MappingNotFoundException("file", xmlFile.toString());
		}
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader(
					xmlFile.toString(), errors, entityResolver).read(xmlFile);
			if (errors.size() != 0) {
				throw new InvalidMappingException("file", xmlFile.toString(),
						(Throwable) errors.get(0));
			}
			addOrReplace(doc);
			return this;
		} catch (InvalidMappingException e) {
			throw e;
		} catch (MappingNotFoundException e) {
			throw e;
		} catch (Exception e) {
			throw new InvalidMappingException("file", xmlFile.toString(), e);
		}
	}

	public MyConfiguration addOrReplaceCacheableFile(String xmlFile)
			throws MappingException {
		return addOrReplaceCacheableFile(new File(xmlFile));
	}

	public MyConfiguration addOrReplaceCacheableFile(File xmlFile)
			throws MappingException {
		try {
			File cachedFile = new File(xmlFile.getAbsolutePath() + ".bin");
			org.dom4j.Document doc = null;

			final boolean useCachedFile = xmlFile.exists()
					&& cachedFile.exists()
					&& xmlFile.lastModified() < cachedFile.lastModified();

			if (useCachedFile) {
				try {
					log.info("Reading mappings from cache file: " + cachedFile);
					doc = (org.dom4j.Document) SerializationHelper
							.deserialize(new FileInputStream(cachedFile));
				} catch (SerializationException e) {
					log.warn("Could not deserialize cache file: "
							+ cachedFile.getPath(), e);
				} catch (FileNotFoundException e) {
					log.warn("I/O reported cached file could not be found : "
							+ cachedFile.getPath(), e);
				}
			}

			// if doc is null, then for whatever reason, the cached file cannot
			// be used...
			if (doc == null) {
				if (!xmlFile.exists()) {
					throw new MappingNotFoundException("file", xmlFile
							.toString());
				}

				log.info("Reading mappings from file: " + xmlFile);
				List errors = new ArrayList();
				try {
					doc = xmlHelper.createSAXReader(xmlFile.getAbsolutePath(),
							errors, entityResolver).read(xmlFile);
					if (errors.size() != 0) {
						throw new MappingException("invalid mapping",
								(Throwable) errors.get(0));
					}
				} catch (DocumentException e) {
					throw new MappingException("invalid mapping", e);
				}

				try {
					log.debug("Writing cache file for: " + xmlFile + " to: "
							+ cachedFile);
					SerializationHelper.serialize((Serializable) doc,
							new FileOutputStream(cachedFile));
				} catch (SerializationException e) {
					log.warn("Could not write cached file: " + cachedFile, e);
				} catch (FileNotFoundException e) {
					log.warn("I/O reported error writing cached file : "
							+ cachedFile.getPath(), e);
				}
			}

			addOrReplace(doc);
			return this;

		} catch (InvalidMappingException e) {
			throw e;
		} catch (MappingNotFoundException e) {
			throw e;
		} catch (Exception e) {
			throw new InvalidMappingException("file", xmlFile.toString(), e);
		}
	}

	public MyConfiguration addOrReplaceXML(String xml) throws MappingException {
		if (log.isDebugEnabled()) {
			log.debug("Mapping XML:\n" + xml);
		}
		try {
			List errors = new ArrayList();
			org.dom4j.Document doc = xmlHelper.createSAXReader("XML String",
					errors, entityResolver).read(new StringReader(xml));
			if (errors.size() != 0) {
				throw new MappingException("invalid mapping",
						(Throwable) errors.get(0));
			}
			addOrReplace(doc);
		} catch (DocumentException e) {
			throw new MappingException(
					"Could not parse mapping document in XML string", e);
		}
		return this;
	}

	public MyConfiguration addOrReplaceURL(URL url) throws MappingException {
		if (log.isDebugEnabled()) {
			log.debug("Reading mapping document from URL:"
					+ url.toExternalForm());
		}
		try {
			addOrReplaceInputStream(url.openStream());
		} catch (InvalidMappingException e) {
			throw new InvalidMappingException("URL", url.toExternalForm(), e
					.getCause());
		} catch (Exception e) {
			throw new InvalidMappingException("URL", url.toExternalForm(), e);
		}
		return this;
	}

	public MyConfiguration addOrReplaceClass(Class persistentClass)
			throws MappingException {
		String name = persistentClass.getName();
		classes.remove(name);
		String mappingResourceName = name.replace('.', '/') + ".hbm.xml";
		log.info("Reading mappings from resource: " + mappingResourceName);
		return addOrReplaceResource(mappingResourceName, persistentClass
				.getClassLoader());
	}

	public MyConfiguration addOrReplaceJar(File jar) throws MappingException {
		log.info("Searching for mapping documents in jar: " + jar.getName());
		JarFile jarFile = null;
		try {
			try {
				jarFile = new JarFile(jar);
			} catch (IOException ioe) {
				throw new InvalidMappingException(
						"Could not read mapping documents from jar: "
								+ jar.getName(), "jar", jar.getName(), ioe);
			}
			Enumeration jarEntries = jarFile.entries();
			while (jarEntries.hasMoreElements()) {
				ZipEntry ze = (ZipEntry) jarEntries.nextElement();
				if (ze.getName().endsWith(".hbm.xml")) {
					log.info("Found mapping document in jar: " + ze.getName());
					try {
						addOrReplaceInputStream(jarFile.getInputStream(ze));
					} catch (Exception e) {
						throw new InvalidMappingException(
								"Could not read mapping documents from jar: "
										+ jar.getName(), "jar", jar.getName(),
								e);
					}
				}
			}
		} finally {
			try {
				if (jarFile != null) {
					jarFile.close();
				}
			} catch (IOException ioe) {
				log.error("could not close jar", ioe);
			}
		}

		return this;
	}

	public MyConfiguration addOrReplaceDirectory(File dir)
			throws MappingException {
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory()) {
				addOrReplaceDirectory(files[i]);
			} else if (files[i].getName().endsWith(".hbm.xml")) {
				addOrReplaceFile(files[i]);
			}
		}
		return this;
	}

	private void addOrReplace(org.dom4j.Document doc) {
		Element hmNode = doc.getRootElement();
		Iterator rootChildren = hmNode.elementIterator();
		while (rootChildren.hasNext()) {
			final Element element = (Element) rootChildren.next();
			final String elementName = element.getName();
			if ("class".equals(elementName)) {
				String entityName = element.attributeValue("entity-name");
				if (entityName == null) {
					entityName = element.attributeValue("name");
					if (entityName == null) {
						throw new MappingException(
								"Unable to determine entity name");
					}
				}
				classes.remove(entityName);
			}
		}
		HbmBinder.bindRoot(doc, createMappings(), CollectionHelper.EMPTY_MAP);
	}

}
